Skip to content

[AIDE-104] Authentication - #82

Merged
chandlerhuff merged 7 commits into
mainfrom
feature/AIDE-104-authentication
Aug 5, 2026
Merged

[AIDE-104] Authentication#82
chandlerhuff merged 7 commits into
mainfrom
feature/AIDE-104-authentication

Conversation

@chandlerhuff

Copy link
Copy Markdown
Member

Summary

Adds authentication to the control plane with Better Auth, replacing the previous no-auth model. Users sign in with email/password and optionally a generic OIDC provider; the first account bootstraps the instance and registration is locked down afterwards. Every request is now resolved to a principal (user session, API key, agent credential, or anonymous) that guards the dashboard, the GraphQL API, the agent WebSocket, and the tools/MCP endpoints. Secret handling is unified around a single APP_SECRET root, and database-stored credentials are now always encrypted with support for key rotation.

Changes

  • Better Auth integration (src/services/auth/): lazy singleton auth instance over Prisma with email/password, optional generic OIDC OAuth, admin plugin with roles, API keys (aide_ prefix), bearer sessions, one-time tokens, and next-cookies.
  • Bootstrap registration (registration.ts): the first registered user claims a short-lived TTL slot; afterwards registration stays disabled until an admin re-enables it via the users page.
  • Principal resolution (principal.ts): resolveRequestPrincipal accepts exactly one credential (session cookie, Bearer, or X-API-Key) and returns a typed principal, rejecting mixed credentials.
  • GraphQL & agent WebSocket auth: HTTP GraphQL requires an authenticated principal; the agent WebSocket resolves credentials from connection params; anonymous access is only allowed for the exact enrollAgent mutation.
  • Tools/MCP auth: authorizeToolRequest now requires a user session or API key instead of the shared TOOLS_API_TOKEN.
  • Mandatory credential encryption: CREDENTIAL_ENCRYPTION_KEY is gone; the key is derived from APP_SECRET, there is no plaintext mode, legacy plaintext rows are swept atomically at startup, and rows sealed under APP_SECRET_PREVIOUS keys are re-encrypted on start.
  • Unified secrets (app-secret.ts): Better Auth secret, credential encryption key, and OTA artifact-token secret are all HKDF-derived from APP_SECRET; build-time uses an ephemeral placeholder that cannot touch stored data. OTA links now survive restarts.
  • Origin allowlist (app-origins.ts): one APP_ORIGINS list now drives CSRF trust, Better Auth base URL/trusted origins, and the Next.js dev-server allowlist, with per-request resolution, wildcards allowed only outside production, and an inferred mode when nothing is configured.
  • Route protection: middleware redirects unauthenticated page requests to /sign-in (preserving returnTo); dashboard routes moved into a (dashboard) group with a server-side session check; new sign-in/register pages and shared AuthForm; the app shell shows the signed-in user with sign-out.
  • Management UI & API: users page (create, delete, set password, ban, revoke sessions, toggle registration) and API keys page (create, rename, disable, delete) backed by new /api/auth/management/* REST endpoints.
  • Mobile OAuth flow: start/complete/redeem endpoints let native apps sign in through the browser and recover a session token via an aide-auth:// deep link.
  • Schema & migrations: new User, Session, Account, Verification, ApiKey, and AuthSettings models plus migrations (including cascading API-key deletes).
  • i18n: auth UI strings added across all four locales.
  • Tests: unit coverage for origin parsing/matching, secret derivation, auth config, registration, principal resolution, GraphQL/tool auth, and credential rotation/sweeping.

Ticket: AIDE-104

…e trusted origins

- Derive the credential database encryption key from APP_SECRET, required
  in all environments, so database storage is always encrypted; support
  rotation via APP_SECRET_PREVIOUS with re-encryption at startup
- Consolidate the server origin allowlist (BETTER_AUTH_URL,
  ALLOWED_DEV_ORIGINS) into src/lib/app-origins.ts, resolving origins per
  request and validating wildcard entries
- Complete mobile OAuth on the trusted origin the device actually reached
  instead of bouncing to the canonical origin
…uest

Replace the hard production error for missing APP_ORIGINS with an
inferred mode: when neither APP_ORIGINS nor PUBLIC_BASE_URL is set, the
server trusts the host each request arrived on and derives canonical
links from it instead of failing at startup. This restores the behavior
of unconfigured deployments while keeping the CSRF check intact, since
browsers set Host to the real destination.

- AppOrigins gains an "inferred" mode with a null canonical origin;
  isTrustedOrigin accepts any host-shaped value in that mode
- OAuth start falls back to the request's own origin when no base URL
  is configured, so mobile sign-in completes on the host the device
  actually reached
- Emit a one-time warning explaining how to pin origins back down
- Update auth runtime config and tests accordingly
@chandlerhuff chandlerhuff self-assigned this Aug 4, 2026
Evaluate trustedOrigins per request only when the allowlist is unconfigured, so a forged Host header can no longer add a foreign origin to the trusted set. Document the security implications in .env.example.
State an explicit cross-origin rule for endpoints authenticated by the
session cookie alone (auth management, GraphQL, tool calls) instead of
relying on SameSite's browser default, reject cross-site WebSocket
handshakes at the server, and bind the mobile OAuth completion leg to a
single-use state cookie so an ambient session cannot be traded for a
one-time token by a top-level cross-site navigation.

Also derive credential encryption from APP_SECRET with APP_SECRET_PREVIOUS
rotation, batch startup encryption and rotation into transactions that
stay inside SQLite's timeout, keep registration-status reads read-only,
and drop admin endpoints that would silently no-op.
Add AUTH_OAUTH_REQUIRE_ISSUER_VALIDATION to control OAuth issuer validation instead of hardcoding it, defaulting to false. Update .env.example docs and the standalone credentials smoke test to use APP_SECRET/APP_ORIGINS, and cover the new setting with tests.
Resolve sessions server-side in a new auth layout instead of optimistically redirecting session cookies in the proxy, so revoked or stale cookies no longer send users to the wrong page. The mobile OAuth start route now builds its completion callback from the trusted forwarded origin, and managed user deletion runs as one conditional SQL statement so concurrent deletes can never empty the user table. Add tests for the layout, OAuth start, and key management, and drop Better Auth's non-atomic removal endpoint.
@chandlerhuff
chandlerhuff merged commit c23bc50 into main Aug 5, 2026
3 checks passed
@chandlerhuff
chandlerhuff deleted the feature/AIDE-104-authentication branch August 5, 2026 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant